﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CRMMetal.Meta;
using System.Net;
using System.CodeDom;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.IO;


namespace CRMMetal
{
    class Program
    {
        // TODO, change these to point to your CRM installation
        static string Url = "http://192.168.150.2:5555/MSCRMServices/2007/MetadataService.asmx";
        static string orgName = "MicrosoftCRM";
        static NetworkCredential creds = new NetworkCredential("administrator", "pass@word1", "litwareinc");

        // TODO, change this to the namespace you want
        static string nameSpace = "LinqtoCRMApplication.CRM";

        static void Main(string[] args)
        {
            CrmAuthenticationToken token = new CrmAuthenticationToken();
            token.AuthenticationType = 0;
            token.OrganizationName = orgName;

            MetadataService service = new MetadataService();
            service.Url = Url;
            service.Credentials = creds;
            service.CrmAuthenticationTokenValue = token;

            RetrieveAllEntitiesRequest allEntitiesRequest = new RetrieveAllEntitiesRequest();
            allEntitiesRequest.RetrieveAsIfPublished = false;
            allEntitiesRequest.MetadataItems = MetadataItems.IncludeRelationships;

            RetrieveAllEntitiesResponse allEntitiesResponse = (RetrieveAllEntitiesResponse)service.Execute(allEntitiesRequest);

            var mtom = allEntitiesResponse.CrmMetadata.OfType<EntityMetadata>().
                SelectMany(e => e.ManyToManyRelationships, (e, d) =>
                    new
                    {
                        intersectname = d.IntersectEntityName,
                        schemaname = d.SchemaName.Replace("_association", ""),
                        ent1Name = d.Entity1LogicalName,
                        ent2Name = d.Entity2LogicalName,
                        ent1Att = d.Entity1IntersectAttribute,
                        ent2Att = d.Entity2IntersectAttribute
                    }
                    ).Distinct().OrderBy(s => s.schemaname);


            foreach (var v in mtom)
            {
                Console.WriteLine("Adding " + v.schemaname);
            }

            CodeCompileUnit targetUnit = new CodeCompileUnit();

            CodeNamespace ns = new CodeNamespace(nameSpace);
            ns.Imports.Add(new CodeNamespaceImport("System"));

            // The stupid codedom API doesn't properly support declarative DOM building, fail.
            CodeTypeDeclarationCollection classes = new CodeTypeDeclarationCollection(
                mtom.Select(_ =>
                    new CodeTypeDeclaration()
                    {
                        //Name = _.name,
                        Name = _.schemaname,
                        IsClass = true,
                        IsPartial = true,
                    }
                )
                .ToArray());

            foreach (CodeTypeDeclaration c in classes)
            {
                int count = mtom.Where(_ => _.schemaname == c.Name).Count();
                if (count > 1)
                    continue; // bad one, multiple with same name

                c.Members.AddRange(new CodeTypeMember[]
                    {
                        new CodeMemberProperty() 
                        { 
                            Name = mtom.Single(_ => _.schemaname == c.Name).ent1Att,
                            Type = new CodeTypeReference(nameSpace + ".Key"),
                            HasGet = true,
                            HasSet = true,
                            Attributes = MemberAttributes.Public,
                        },
                        new CodeMemberProperty() 
                        { 
                            Name = mtom.Single(_ => _.schemaname == c.Name).ent2Att,
                            Type = new CodeTypeReference(nameSpace + ".Key"),
                            HasGet = true,
                            HasSet = true,
                            Attributes = MemberAttributes.Public,
                        }
                    }
                );

                // goddam codedom doesn't support automatic properties, have to add something to getters. fail.
                foreach (CodeMemberProperty p in c.Members)
                {
                    // just have it return null
                    p.GetStatements.Add(new CodeMethodReturnStatement(new CodePrimitiveExpression(null)));
                }
            }
            ns.Types.AddRange(classes);
            targetUnit.Namespaces.Add(ns);

            CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
            CodeGeneratorOptions options = new CodeGeneratorOptions();
            options.BracingStyle = "C";
            using (StreamWriter sourceWriter = new StreamWriter("ManyToManyClasses.cs"))
            {
                provider.GenerateCodeFromCompileUnit(
                    targetUnit, sourceWriter, options);
            }

            Console.WriteLine("");
            Console.WriteLine("Classes generated, press any key to exit");
            Console.ReadKey();
        }
    }
}
